package jp.sinmetal.trainer.service;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import jp.sinmetal.trainer.condition.EndBattleLogCondition;
import jp.sinmetal.trainer.dto.ConventionMemberDto;
import jp.sinmetal.trainer.entity.Convention;
import jp.sinmetal.trainer.entity.ConventionLog;
import jp.sinmetal.trainer.util.DateUtil;

import org.seasar.framework.util.StringUtil;

/**
 * 大会ファサードサービス
 * @author sin
 *
 */
public class ConventionFacadeService {

	/**
	 * 大会サービス
	 * @author sin
	 */
	@Resource
	ConventionService conventionService;

	/**
	 * 大会参加者サービス
	 * @author sin
	 */
	@Resource
	ConventionMemberService conventionMemberService;

	/**
	 * 大会ログサービス
	 * @author sin
	 */
	@Resource
	ConventionLogService conventionLogService;

	/**
	 * 大会一覧を返す
	 * @author sin
	 *
	 * @return
	 */
	public List<Convention> findAllConvention() {
		return conventionService.findAllConvention();
	}

	/**
	 * 大会IDを基にエンティティを返す
	 * @author sin
	 *
	 * @param conventionId
	 * @return
	 */
	public Convention findConventionById(Integer conventionId) {
		return conventionService.findById(conventionId);
	}

	/**
	 * 大会参加者一覧を返す
	 * @author sin
	 *
	 * @param conventionId
	 * @return
	 */
	public List<ConventionMemberDto> findConventionMemberByConventionId(Integer conventionId) {
		return conventionMemberService.findByConventionId(conventionId);
	}

	/**
	 * 大会追加
	 * @author sin
	 *
	 * @param convention
	 * @return 追加数
	 */
	public int insertConvention(Convention entity) {
		return conventionService.insertConvention(entity);
	}

	/**
	 * 大会更新
	 *
	 * @param entity
	 * @return 更新数
	 */
	public int updateConvention(Convention entity) {
		return conventionService.updateConvention(entity);
	}

	/**
	 * 対戦中の大会ログ一覧を返す
	 *
	 * @param conventionId
	 * @return
	 */
	public List<ConventionLog> findDuringBattleConventionLogAll(Integer conventionId) {
		return conventionLogService.findDuringBattleAll(conventionId);
	}

	/**
	 * 対戦終了した大会ログ数を返す
	 * @author sin
	 *
	 * @param conventionId
	 * @return
	 */
	public Long getEndBattleConventionLogCount(Integer conventionId) {
		return conventionLogService.getEndBattleLogCount(conventionId);
	}

	/**
	 * 対戦終了した大会ログ一覧を返す
	 *
	 * @param condition
	 * @return
	 */
	public List<ConventionLog> findEndBattleConventionLogs(EndBattleLogCondition condition) {
		return conventionLogService.findEndBattleLogs(condition);
	}


	/**
	 * マッチング
	 * @author sin
	 *
	 * @param conventionId
	 * @param isCoercion
	 * @return マッチングした場合は空文字、それ以外はメッセージ
	 */
	public String matching(Integer conventionId, boolean isCoercion) {
		String matchingMessage = getMatchingMessage(conventionId, isCoercion);
		if (StringUtil.isNotEmpty(matchingMessage)) {
			return matchingMessage;
		}

		//対戦数に応じて、マッチング方式を変える。閾値は適当。
		final int NOT_MATCHING_LIMIT = 2;
		long conventionMemberCount = conventionMemberService.getCount(conventionId);
		long conventionLogCount = conventionLogService.getCount(conventionId);
		if (conventionMemberCount * NOT_MATCHING_LIMIT > conventionLogCount) {
			matching4NotMatched(conventionId);
		} else {
			matching4WinCount(conventionId);
		}
		return "";
	}

	/**
	 * 対戦結果登録
	 * @author sin
	 *
	 * @param conventionLog
	 */
	public void registBattleResult(ConventionLog conventionLog) {
		conventionLogService.updateConventionLog(conventionLog);
	}

	/**
	 * マッチングメッセージを返す
	 * @author sin
	 *
	 * @param conventionId
	 * @param isCoercion
	 * @return マッチングできる場合は空文字,それ以外は、メッセージ
	 */
	protected String getMatchingMessage(Integer conventionId, boolean isCoercion) {
		if (isCoercion) return "";

		Convention convention = conventionService.findById(conventionId);

		if (!DateUtil.isOverTime(convention.conventionDate)) {
			return "大会開催時刻が、まだ来ていません。";
		}

		if (isEnd(convention)) {
			Long matchingCount = conventionLogService.getMatchingCount(conventionId);
			if (matchingCount == 0) {
				return "大会は終了致しました。";
			}
			return "マッチングは終了致しました。残りの対戦が完了すると、大会が終了致します。";
		}

		return getFreeMemberExceedConventionMemberHalfMessage(conventionId);
	}

	/**
	 * 大会参加者の半数を超える人数がフリーか
	 * @author sin
	 *
	 * @param conventionId
	 * @return
	 */
	protected String getFreeMemberExceedConventionMemberHalfMessage(Integer conventionId) {
		Long diff = conventionMemberService.getFreeMemberCount(conventionId) - (conventionMemberService.getCount(conventionId) / 2);
		if (diff < 0) {
			return String.format("マッチングされるには、後%s人の未マッチング者が必要です。", diff * -1 + 1);
		}
		return "";
	}

	/**
	 * 大会終了条件を満たしているか
	 * @author sin
	 *
	 * @param convention
	 * @return
	 */
	protected boolean isEnd(Convention convention) {
		switch (convention.endState) {
		case Convention.END_STATE_WIN_COUNT:
			return isEndWinCount(convention);
		case Convention.END_STATE_LIMIT_TIME:
			return isEndLimitTime(convention);
		default:
			return false;
		}
	}

	/**
	 * 終了勝利数を満たしているか
	 * @author sin
	 *
	 * @param convention
	 * @return
	 */
	protected boolean isEndWinCount(Convention convention) {
		List<ConventionMemberDto> winCountCompleteMembers = conventionMemberService.findEndWinCount(convention.conventionId, convention.endWinCount);
		return (winCountCompleteMembers.size() > 0);
	}

	/**
	 * 終了時刻を過ぎているか
	 * @author sin
	 *
	 * @param convention
	 * @return
	 */
	protected boolean isEndLimitTime(Convention convention) {
		final int LAST_MATCHING_INTERVAL_MINUTE = 20;
		Date matchingEndTime = DateUtil.getAddMinuteTime(convention.conventionDate, convention.endLimitTimeMin - LAST_MATCHING_INTERVAL_MINUTE);
		return (DateUtil.isOverTime(matchingEndTime, DateUtil.getNow()));
	}

	/**
	 * 勝利回数が近くなるようにマッチング
	 * @author sin
	 *
	 * @param conventionId
	 */
	protected void matching4WinCount(Integer conventionId) {
		List<ConventionMemberDto> freeConventionMemberDtos = conventionMemberService.findFreeMembers(conventionId, "winCount");

		// {winCount, List<ConventionMemberDto> のマップ
		Map<Integer, List<ConventionMemberDto>> memberEachWinCountListMap = getMemberEachWinCountListMap(freeConventionMemberDtos);
		List<ConventionLog> conventionLogs = buildeConventionLogs(memberEachWinCountListMap);
		conventionLogService.insertItems(conventionLogs);
	}

	/**
	 * なるべく未マッチング同士がマッチングされるようにマッチング
	 * @author sin
	 *
	 * @param conventionId
	 */
	protected void matching4NotMatched(Integer conventionId) {
		List<ConventionMemberDto> freeConventionMemberDtos = conventionMemberService.findFreeMembers(conventionId, "battleCount");
		List<ConventionLog> endBattleConventionLogs = conventionLogService.findEndBattleLogs(EndBattleLogCondition.getInstanceByConventionId(conventionId));

		Map<Integer, List<Integer>> endBattleConventionLogMap = new HashMap<Integer, List<Integer>>();
		for (ConventionLog conventionLog : endBattleConventionLogs) {
			addEndBattleConventionMemberList(endBattleConventionLogMap, conventionLog.conventionMemberId1st, conventionLog.conventionMemberId2nd);
			addEndBattleConventionMemberList(endBattleConventionLogMap, conventionLog.conventionMemberId2nd, conventionLog.conventionMemberId1st);
		}

		List<ConventionLog> conventionLogs = buildeConventionLogs4NotMatched(freeConventionMemberDtos, endBattleConventionLogMap);
		conventionLogService.insertItems(conventionLogs);
	}

	/**
	 * 対戦終了済みメンバー一覧追加
	 * @author sin
	 *
	 * @param endBattleConventionLogMap
	 * @param mainConventionMemberId
	 * @param subConventionMemberId
	 */
	protected void addEndBattleConventionMemberList(Map<Integer, List<Integer>> endBattleConventionLogMap,
			Integer mainConventionMemberId, Integer subConventionMemberId) {
		if (!endBattleConventionLogMap.containsKey(mainConventionMemberId)){
			endBattleConventionLogMap.put(mainConventionMemberId, new ArrayList<Integer>());
		}
		List<Integer> endBattleConventionMemberIds = endBattleConventionLogMap.get(mainConventionMemberId);
		endBattleConventionMemberIds.add(subConventionMemberId);
	}

	/**
	 * 渡された大会参加者一覧を、勝利数ごとの一覧にして返す
	 * @author sin
	 *
	 * @param conventionMemberDtos
	 * @return {winCount, List<ConventionMemberDto> のマップ
	 */
	protected Map<Integer, List<ConventionMemberDto>> getMemberEachWinCountListMap(List<ConventionMemberDto> conventionMemberDtos) {
		// {winCount, List<ConventionMemberDto> のマップ
    	Map<Integer, List<ConventionMemberDto>> memberEachWinCountListMap = new LinkedHashMap<Integer, List<ConventionMemberDto>>();

    	for (ConventionMemberDto conventionMemberDto : conventionMemberDtos) {
    		Integer key = conventionMemberDto.winCount;
    		// winCountがマップに無ければ作成
    		if (!memberEachWinCountListMap.containsKey(key)) {
    			memberEachWinCountListMap.put(key, new ArrayList<ConventionMemberDto>());
    		}
    		List<ConventionMemberDto> memberEachWinCountList = memberEachWinCountListMap.get(key);
    		memberEachWinCountList.add(conventionMemberDto);
    	}

    	return memberEachWinCountListMap;
	}

	/**
	 * 渡された大会参加者から、大会ログを作成
	 * @author sin
	 *
	 * @param memberEachCountListMap
	 * @return
	 */
	protected List<ConventionLog> buildeConventionLogs(Map<Integer, List<ConventionMemberDto>> memberEachCountListMap) {
		//TODO 順番通りにマッチングされていくから、改良する必要あり
    	List<ConventionLog> conventionLogs = new ArrayList<ConventionLog>();
    	ConventionMemberDto workMember = null;
    	for (Integer winCount : memberEachCountListMap.keySet()) {
    		List<ConventionMemberDto> memberEachWinCountList = memberEachCountListMap.get(winCount);
    		for (ConventionMemberDto conventionMember : memberEachWinCountList) {
    			if (workMember != null) {
    				ConventionLog conventionLog = buildeConventionLog(workMember, conventionMember);
    				conventionLogs.add(conventionLog);
    				workMember = null;
    			} else {
    				workMember = conventionMember;
    			}
    		}
    	}

    	return conventionLogs;
	}

	/**
	 * 渡された大会参加者から、大会ログを作成
	 * なるべく未マッチング同士がマッチングされるようにする
	 * @author sin
	 *
	 * @param memberEachCountListMap
	 * @return
	 */
	protected List<ConventionLog> buildeConventionLogs4NotMatched(List<ConventionMemberDto> freeConventionMemberDtos, Map<Integer, List<Integer>> endBattleConventionLogMap) {
    	List<ConventionLog> conventionLogs = new ArrayList<ConventionLog>();
    	ConventionMemberDto workMember = null;
    	for (ConventionMemberDto conventionMember : freeConventionMemberDtos) {
    		if (workMember != null) {
    			if (isMatched(endBattleConventionLogMap, workMember.conventionMemberId, conventionMember.conventionMemberId)) {
    				continue;
    			}
    			ConventionLog conventionLog = buildeConventionLog(workMember, conventionMember);
    			conventionLogs.add(conventionLog);
    			workMember = null;
    		} else {
    			workMember = conventionMember;
    		}
    	}

    	return conventionLogs;
	}

	/**
	 * マッチングされたことがあるかどうか
	 * @author sin
	 *
	 * @param endBattleConventionLogMap
	 * @param mainConventionMemberId
	 * @param subConventionMemberId
	 * @return
	 */
	protected boolean isMatched(Map<Integer, List<Integer>> endBattleConventionLogMap, Integer mainConventionMemberId, Integer subConventionMemberId) {
		if (endBattleConventionLogMap.containsKey(mainConventionMemberId)) {
			List<Integer> endBattleConventionMemberIds = endBattleConventionLogMap.get(mainConventionMemberId);
			for (Integer conventionMemberId : endBattleConventionMemberIds) {
				if (conventionMemberId.equals(subConventionMemberId)) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * 大会ログ作成
	 * @author sin
	 *
	 * @param matchingMember1st
	 * @param matchingMember2nd
	 * @return
	 */
	protected ConventionLog buildeConventionLog(ConventionMemberDto matchingMemberDto1st, ConventionMemberDto matchingMemberDto2nd) {
		ConventionLog conventionLog = new ConventionLog();
		conventionLog.conventionId = matchingMemberDto1st.conventionId;
		conventionLog.conventionMemberId1st = matchingMemberDto1st.conventionMemberId;
		conventionLog.conventionMemberId2nd = matchingMemberDto2nd.conventionMemberId;
		conventionLog.conventionLogState = ConventionLog.STATE_NORMAL;

		Date today = DateUtil.getNow();
		conventionLog.entryDate = today;
		conventionLog.updateDate = today;

		return conventionLog;
	}
}
